/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.cef.generator.coreGenerator.base.entitygen.method.copy;

import com.inspur.edp.caf.generator.baseInfo.ParameterInfo;
import com.inspur.edp.caf.generator.baseInfo.TypeInfo;
import com.inspur.edp.caf.generator.method.ClassMethodGenerator;
import com.inspur.edp.caf.generator.utils.ExpressionUtils;
import com.inspur.edp.cef.api.message.CefException;
import com.inspur.edp.cef.designtime.api.IGspCommonDataType;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.core.data.EntityDataCollection;
import com.inspur.edp.cef.entity.entity.ICefData;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.gencommon.CefPropertyInfo;
import com.inspur.edp.cef.gencommon.MediateType;
import com.inspur.edp.cef.generator.common.MediatePropertyInfo;
import com.inspur.edp.cef.generator.coreGenerator.base.entitygen.EntityDataUtils;
import com.inspur.edp.cef.generator.coreGenerator.base.entitygen.EntityGeneratorContext;
import com.inspur.edp.cef.generator.coreGenerator.base.entitygen.method.GetResInfoGenerator;
import com.inspur.edp.cef.generator.overall.GeneratorUtil;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.InfixExpression.Operator;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;

//    
//        var result = this.MemberwiseClone() as IXXData;
//        
//        if(soItems!=null)
//        {
//        result.soItems=new new EntityDataCollection();
//        foreach(ISOItem item in soItems)
//        {
//            result.soItems.Add(item.copy() as IEntityData);
//        }
//        }
//     
public class CopyMethodGenerator extends ClassMethodGenerator {

  private static final String varResult = "result";
  private final EntityGeneratorContext parentContext;
  private String privateDataType;
  private List<MediatePropertyInfo> fields;

  public CopyMethodGenerator(EntityGeneratorContext parentContext, String belongDataType,List<MediatePropertyInfo> fields) {
    this.parentContext = parentContext;
    this.fields = fields;
    privateDataType = belongDataType;
//    this.dataType = dataType;
  }

  public final String getDataType() {
    return privateDataType;
  }

  @Override
  protected Block buildMethodBody() {
    Block result = ast.newBlock();
    TryStatement tryStatement = ast.newTryStatement();
    setTryBody(tryStatement);
    setTryCatch(tryStatement);
    result.statements().add(tryStatement);
    return result;
  }

  private void setTryCatch(TryStatement tryStatement) {

    CatchClause catchClause = ast.newCatchClause();
    setCatchClauseException(catchClause);
    setCatchClauseBody(catchClause);
    tryStatement.catchClauses().add(catchClause);
  }

  private void setCatchClauseException(CatchClause catchClause) {
    SingleVariableDeclaration singleVariableDeclaration = ast.newSingleVariableDeclaration();
    singleVariableDeclaration.setType(
        new com.inspur.edp.caf.generator.baseInfo.TypeInfo(CloneNotSupportedException.class)
            .getType(ast));
    singleVariableDeclaration.setName(ast.newSimpleName("e"));
    catchClause.setException(singleVariableDeclaration);
  }

  private void setCatchClauseBody(CatchClause catchClause) {
    Block catchBodyBlock = ast.newBlock();
    addImport(CefException.class.getTypeName());
    ThrowStatement throwStatement = GeneratorUtil.getThrowStatementWithParamName(ast,"e");
//    ClassInstanceCreation classInstanceCreation = ast.newClassInstanceCreation();
//    classInstanceCreation.setType(
//        new com.inspur.edp.caf.generator.baseInfo.TypeInfo(RuntimeException.class).getType(ast));
//    classInstanceCreation.arguments().add(ast.newSimpleName("e"));
//    throwStatement.setExpression(classInstanceCreation);

    catchBodyBlock.statements().add(throwStatement);
    catchClause.setBody(catchBodyBlock);
  }

  private void setTryBody(TryStatement tryStatement) {

    Block block = ast.newBlock();
    addCloneMethod(block);
    addSetValues(block);
    addReturnValue(block);
    tryStatement.setBody(block);
  }

  private void addCloneMethod(Block block) {
    VariableDeclarationFragment fragment = ast.newVariableDeclarationFragment();
    fragment.setName(ast.newSimpleName(varResult));
    CastExpression castExpression = ast.newCastExpression();
    fragment.setInitializer(castExpression);
    castExpression.setType(ast.newSimpleType(ast.newName(getDataType())));
    MethodInvocation methodInvocation = ast.newMethodInvocation();
    castExpression.setExpression(methodInvocation);
    methodInvocation.setName(ast.newSimpleName("clone"));
    methodInvocation.setExpression(ast.newThisExpression());

    VariableDeclarationStatement variableDeclarationStatement = ast
        .newVariableDeclarationStatement(fragment);
    variableDeclarationStatement.setType(ast.newSimpleType(ast.newName(getDataType())));
    block.statements().add(variableDeclarationStatement);
  }


  private void addSetValues(Block block) {
    List<IGspCommonDataType> childDataTypes = new ArrayList<>();
    for (MediatePropertyInfo field:fields) {
      if(field.getNode() != null){
        childDataTypes.add(field.getNode());
      }
      if(field.getElement() == null){
        continue;
      }
      if(field.getExtendInfos().containsKey("AssoUdtAtrribute"))
        continue;
      if(field.getElement().getIsUdt())
        addCefDataTypeProperty(block, field);
      //TODO: java Date是可变对象
//      else
//        addNormalProperty(block,field);
    }
    for (IGspCommonDataType childNode:childDataTypes) {
      addSetChildDataProperty(block,childNode);
    }
  }

  private void addNormalProperty(Block block, MediatePropertyInfo field) {
    MethodInvocation methodInvocation = ast.newMethodInvocation();
    methodInvocation.setExpression(ast.newSimpleName(varResult));
    methodInvocation.setName(ast.newSimpleName("set"+field.getPropertyName()));
    MethodInvocation methodInvocation1 = ast.newMethodInvocation();
    methodInvocation.arguments().add(methodInvocation1);
    methodInvocation1.setExpression(ast.newThisExpression());
    methodInvocation1.setName(ast.newSimpleName("get"+field.getPropertyName() ));
    ExpressionStatement expressionStatement = ast.newExpressionStatement(methodInvocation);
    block.statements().add(expressionStatement);
  }

  private Boolean addCefDataTypeProperty(Block block, MediatePropertyInfo field) {
//
    IfStatement ifStatement = ast.newIfStatement();
    addCefDataTypeIfExpression(block, field.getElement(), ifStatement);
    addCefDataTypeIfThenStatement(block, field.getElement(), ifStatement);
    return true;
  }

  private void addCefDataTypeIfStatement(Block block, CefPropertyInfo propertyInfo) {


  }

  private void addCefDataTypeIfExpression(Block block, IGspCommonField field,
      IfStatement ifStatement) {
    InfixExpression infixExpression = ast.newInfixExpression();
    ifStatement.setExpression(infixExpression);
    infixExpression.setOperator(InfixExpression.Operator.EQUALS);

    MethodInvocation leftInvocation = ast.newMethodInvocation();
    infixExpression.setLeftOperand(leftInvocation);
    leftInvocation.setName(ast.newSimpleName("get"+ field.getLabelID()));

    infixExpression.setRightOperand(ast.newNullLiteral());
  }

  private void addCefDataTypeIfThenStatement(Block block, IGspCommonField field,
      IfStatement ifStatement) {
    MethodInvocation methodInvocation = ast.newMethodInvocation();
    methodInvocation.setName(ast.newSimpleName("set"+field.getLabelID()));
    methodInvocation.setExpression(ast.newSimpleName(varResult));

    MethodInvocation methodInvocation1 = ast.newMethodInvocation();
    methodInvocation.arguments().add(methodInvocation1);
    methodInvocation1.setName(ast.newSimpleName("get"+ field.getLabelID()));

    ExpressionStatement expressionStatement = ast.newExpressionStatement(methodInvocation);
    ifStatement.setThenStatement(expressionStatement);
  }


  private Boolean addSetChildDataProperty(Block block,  IGspCommonDataType childNode) {
    addSetChildIfStatement(block, childNode);
    return true;
  }

  private void addSetChildNewField(Block block, CefPropertyInfo propertyInfo) {

  }

  private void addSetChildIfStatement(Block block, IGspCommonDataType childNode) {
    IfStatement ifStatement = ast.newIfStatement();
    addSetChildIfExpression(block,  childNode,ifStatement);
    addSetChildIfThenStatement(block,  childNode, ifStatement);
    block.statements().add(ifStatement);
  }

  private void addSetChildIfExpression(Block block, IGspCommonDataType childNode,IfStatement ifStatement) {
    InfixExpression infixExpression = ast.newInfixExpression();
    ifStatement.setExpression(infixExpression);

    infixExpression.setOperator(Operator.NOT_EQUALS);
    infixExpression.setLeftOperand(
        ast.newSimpleName(EntityDataUtils.getDataChildEntityFieldName(childNode.getCode())));
    infixExpression.setRightOperand(ast.newNullLiteral());
  }

  private void addSetChildIfThenStatement(Block block, IGspCommonDataType childNode,IfStatement ifStatement) {
    Block thenBlock = ast.newBlock();
    ifStatement.setThenStatement(thenBlock);
    addSetChildSetChildValuesStatement(thenBlock, childNode, ifStatement);
    addSetChildSetChildAddItemsStatement(thenBlock,childNode, ifStatement);
  }

  private void addSetChildSetChildValuesStatement(Block thenBlock,  IGspCommonDataType childNode,IfStatement ifStatement) {
    Assignment assignment = ast.newAssignment();
    assignment.setOperator(Assignment.Operator.ASSIGN);

    assignment.setLeftHandSide(ast.newQualifiedName
        (ast.newSimpleName(varResult), ast.newSimpleName(
            EntityDataUtils.getDataChildEntityFieldName(childNode.getCode()))));

    ClassInstanceCreation classInstanceCreation = ast.newClassInstanceCreation();
    classInstanceCreation.setType(
        new com.inspur.edp.caf.generator.baseInfo.TypeInfo(EntityDataCollection.class)
            .getType(ast));
    MediateType entityType = parentContext.getProjContext().getEntityMediateTypes().get(childNode.getCode());
    MethodInvocation invocation = ExpressionUtils
        .getMethodInvocation(ast, entityType.getFullName(), GetResInfoGenerator.MethodName);
    classInstanceCreation.arguments().add(invocation);

    assignment.setRightHandSide(classInstanceCreation);

    thenBlock.statements().add(ast.newExpressionStatement(assignment));

  }

  private void addSetChildSetChildAddItemsStatement(Block thenBlock, IGspCommonDataType childNode,IfStatement ifStatement) {
    EnhancedForStatement enhancedForStatement = ast.newEnhancedForStatement();
    addSetEnhanceForParameter(enhancedForStatement, childNode);
    addSetEnhanceForExpression(enhancedForStatement, childNode);
    addSetEnhanceForBody(enhancedForStatement, childNode);
    thenBlock.statements().add(enhancedForStatement);
  }

  private void addSetEnhanceForParameter(EnhancedForStatement enhancedForStatement,
       IGspCommonDataType childNode) {
    SingleVariableDeclaration singleVariableDeclaration = ast.newSingleVariableDeclaration();
    singleVariableDeclaration.setType(
        new com.inspur.edp.caf.generator.baseInfo.TypeInfo(IEntityData.class).getType(ast));
    singleVariableDeclaration.setName(ast.newSimpleName("item"));
    enhancedForStatement.setParameter(singleVariableDeclaration);
  }

  private void addSetEnhanceForExpression(EnhancedForStatement enhancedForStatement,
      IGspCommonDataType childNode) {
    enhancedForStatement.setExpression(
        ast.newSimpleName(EntityDataUtils.getDataChildEntityFieldName(childNode.getCode())));
  }

  private void addSetEnhanceForBody(EnhancedForStatement enhancedForStatement,
      IGspCommonDataType childNode) {
    Block forBodyBlock = ast.newBlock();
    addForBodyExpression(forBodyBlock, childNode);
    enhancedForStatement.setBody(forBodyBlock);
  }

  private void addForBodyExpression(Block forBodyBlock,  IGspCommonDataType childNode) {
    MethodInvocation methodInvocation = ast.newMethodInvocation();
    methodInvocation.setExpression(
        ast.newQualifiedName(ast.newSimpleName(varResult), ast.newSimpleName(
            EntityDataUtils.getDataChildEntityFieldName(childNode.getCode()))));
    methodInvocation.setName(ast.newSimpleName("add"));
    addForBodyAddItem(methodInvocation,childNode);
    ExpressionStatement expressionStatement = ast.newExpressionStatement(methodInvocation);
    forBodyBlock.statements().add(expressionStatement);
  }

  private void addForBodyAddItem(MethodInvocation methodInvocation,  IGspCommonDataType childNode) {
    CastExpression castExpression = ast.newCastExpression();
    castExpression.setType(
        new com.inspur.edp.caf.generator.baseInfo.TypeInfo(IEntityData.class).getType(ast));
    MethodInvocation methodInvocation1 = ast.newMethodInvocation();
    methodInvocation1.setName(ast.newSimpleName("copy"));
    methodInvocation1.setExpression(ast.newSimpleName("item"));
    castExpression.setExpression(methodInvocation1);

    methodInvocation.arguments().add(castExpression);
  }


  private void addReturnValue(Block block) {
    ReturnStatement returnStatement = ast.newReturnStatement();
    block.statements().add(returnStatement);
    returnStatement.setExpression(ast.newSimpleName(varResult));
  }

  @Override
  protected ArrayList<Modifier.ModifierKeyword> getAccessModifiers() {
    ArrayList<Modifier.ModifierKeyword> list = new ArrayList<>();
    list.add(Modifier.ModifierKeyword.PROTECTED_KEYWORD);
    return list;
  }

  @Override
  protected String getMethodName() {
    return "innerCopy";
  }

  @Override
  protected ArrayList<ParameterInfo> getParameterCollection() {
    return null;
  }

  @Override
  protected TypeInfo getReturnType() {
    return new TypeInfo(ICefData.class.getTypeName());
  }
}
